import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;
import org.apache.http.*;
import org.apache.http.entity.*;
import org.apache.http.impl.*;
import org.apache.http.params.*;
import org.apache.http.protocol.*;

/** A simple server that will send back responses in Javascript callbacks. */
final class Server {

  /** Return this and we won't respond to request. */
  public final static String DONT_RESPOND = "dont.respond";

  static final int PORT = 4444;
  private final Map<String,Handler> handlers = new HashMap<String,Handler>();
  private final Debuggable debugger;
  private RequestListenerThread mainThread;

  /**
   * Something that can be debugged
   */
  interface Debuggable {
    
    /** 
     * Called to note an event <code>msg</code>.
     *
     * @param d    date of the event
     * @param msg  message to show
     * @param c    color of the message to show
     */
    void note(Date d, Object msg, Color c);

    /**
     * Called to note that a {@link Handler} was used.
     *
     * @param handler Handler being used.
     */
    void note(Handler handler);
    
  }

  Server(Debuggable debugger) {
    this.debugger = debugger;
  }

  private class MyHandler
    implements HttpRequestHandler {

    public void handle(HttpRequest request, 
                       HttpResponse response, 
                       HttpContext context) 
      throws HttpException, IOException {
      String method = request.getRequestLine().getMethod().toUpperCase();
      if(!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
        throw new MethodNotSupportedException(method + " method not supported");
      }
      String target = request.getRequestLine().getUri();
      if (target.startsWith("/")) target = target.substring(1);
      Map<String,String> args = Util.parseArgs(target);
      int ihuh = target.indexOf("?");
      if (ihuh != -1) target = target.substring(0, ihuh);
      String callback = (String)args.get("callback");
      if (callback == null) callback = "callback";
      note(target, Constants.COLOR_INCOMING);
      for (String key : args.keySet()) {
        note(" - " + key + "=" + args.get(key), Constants.COLOR_INCOMING);
      }
      Handler h = getHandler(target);
      debugger.note(h);
      String res = h != null ? h.handle(args) : "NO";

      note(res, Constants.COLOR_OUTGOING);
      String s;
      if (res.equals(DONT_RESPOND)) {
        s = dontRespondString();
        try{Thread.sleep(5000);} catch(Exception e) {}
      } else {
        s = callback + "('" + res.replace('\'', '"') + "');";
      }
      note("Responding... " + s, Constants.COLOR_DEBUG);
      response.setStatusCode(200);
      response.setEntity(new StringEntity(s));
    }
    
    public MyHandler() {
    }
  }

  private static String dontRespondString() {
    return DontRespond.getString();
  }

  private class RequestListenerThread extends Thread {

    public void run() {
      note("Listening on port " + serverSocket.getLocalPort());
      while(!Thread.interrupted())  {
        try {
          Socket socket = serverSocket.accept();
          DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
          note("Incoming connection from " + socket.getInetAddress());
          conn.bind(socket, params);
          BasicHttpProcessor httpproc = new BasicHttpProcessor();
          httpproc.addInterceptor(new ResponseDate());
          httpproc.addInterceptor(new ResponseServer());
          httpproc.addInterceptor(new ResponseContent());
          httpproc.addInterceptor(new ResponseConnControl());
          HttpRequestHandlerRegistry reqistry 
            = new HttpRequestHandlerRegistry();
          
          // We want all the requests!
          reqistry.register("*", new MyHandler());
          HttpService httpService = 
            new HttpService(httpproc, 
                            new DefaultConnectionReuseStrategy(), 
                            new DefaultHttpResponseFactory());
          httpService.setParams(params);
          httpService.setHandlerResolver(reqistry);
          Thread t = new WorkerThread(httpService, conn);
          t.setDaemon(true);
          t.start();
          continue;
        }
        catch(InterruptedIOException ex) { }
        catch(IOException e) {}
        break;
      }
    }

    final ServerSocket serverSocket;
    private final HttpParams params = new BasicHttpParams();

    public RequestListenerThread(int port) throws IOException {
      serverSocket = new ServerSocket(port);
      params.setIntParameter("http.socket.timeout", 5000)
        .setIntParameter("http.socket.buffer-size", 8192)
        .setBooleanParameter("http.connection.stalecheck", false)
        .setBooleanParameter("http.tcp.nodelay", true)
        .setParameter("http.origin-server", "Jakarta-HttpComponents/1.1");
    }
  }

  class WorkerThread extends Thread {

    public void run() {
      note("New connection thread", Constants.COLOR_DEBUG);
      HttpContext context = new HttpExecutionContext(null);
      try {
        for(; !Thread.interrupted() && conn.isOpen(); 
            httpservice.handleRequest(conn, context));
      }
      catch(ConnectionClosedException ex) {}
      catch(IOException ex) {}
      catch(HttpException ex) {}
      finally {
        try {
          conn.shutdown();
        }
        catch(IOException ioexception) { }
      }
      return;
    }

    private final HttpService httpservice;
    private final HttpServerConnection conn;

    public WorkerThread(HttpService httpservice, HttpServerConnection conn) {
      this.httpservice = httpservice;
      this.conn = conn;
    }
  }

  public boolean registerHandler(Handler h) {
    return handlers.put(hash(h.name()), h) == h;
  }

  Iterable<Handler> getHandlers() {
    return handlers.values();
  }

  public void start() throws IOException {
    mainThread = new RequestListenerThread(4444);
    mainThread.setDaemon(false);
    mainThread.start();
    note("Outgoing...", Constants.COLOR_OUTGOING);
    note("Incoming...", Constants.COLOR_INCOMING);
    note("Debugging...", Constants.COLOR_DEBUG);
  }

  public void stop() {
    try {
      mainThread.serverSocket.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
    try {
      if(mainThread != null)
        mainThread.join();
    }
    catch(InterruptedException e) {
      e.printStackTrace();
    }
  }

  protected void note(Object msg) {
    note(msg, Constants.COLOR_DEBUG);
  }

  private String hash(String s) {
    return s.toLowerCase();
  }

  private Handler getHandler(String target) {
    return (Handler)handlers.get(hash(target));
  }

  private void note(Object msg, Color c) {
    debugger.note(new Date(), msg, c);
  }

}
